﻿using System;
using System.Collections.Generic;
using System.Drawing;
using RaytracerLib.Layers;
using RaytracerLib.MathLib;
using TomanuExtensions;
using RaytracerLib.Layers.OverlayCorrectors;
using System.Linq;
using RaytracerLib.Postprocessing;
using System.Diagnostics;
using YAXLib;
using RaytracerLib.Samplers;

namespace RaytracerLib.Resamplers
{
    public class LocalGridResampler : InterpolateResampler
    {
        private double[,] m_weights;

        [YAXNode]
        public int Subresolution = 2;

        private double m_scale;

        internal override void RenderStart(RenderStartPhase a_phase)
        {
            base.RenderStart(a_phase);

            if (a_phase == RenderStartPhase.PrepareObjectToRender)
            {
                var sampler = Film.Sampler as UniformSampler;

                if (sampler == null)
                    throw new InvalidOperationException();
                    
                if (Subresolution % 2 == 0)
                    throw new InvalidOperationException();

                m_scale = Subresolution * sampler.Subresolution;
                PrepareWeights();
            }
        }

        private void PrepareWeights()
        {
            List<List<PixelWeight>> horz_weights, vert_weights;
            ResizerResampler.PrecalculateWeights((int)m_scale, (int)m_scale, 1, 1, 
                out horz_weights, out vert_weights, Filter, false);
            double[] weights = horz_weights[0].Select(pw => pw.Weight).ToArray();

            Debug.Assert(weights.Length % 2 == 1);

            m_weights = new double[weights.Length, weights.Length];

            for (int x = 0; x < weights.Length; x++)
            {
                double wx = weights[x];

                for (int y = 0; y < weights.Length; y++)
                {
                    double wy = weights[y];
                    m_weights[x, y] = wx * wy;
                }
            }
        }

        private double GetWeight(double a_dx, double a_dy)
        {
            int x = (a_dx * m_scale).Round();
            x += (m_weights.GetLength(0) - 1) / 2;

            if (x < 0)
                return 0;
            else if (x >= m_weights.GetLength(0))
                return 0;

            int y = (a_dy * m_scale).Round();
            y += (m_weights.GetLength(1) - 1) / 2;

            if (y < 0)
                return 0;
            else if (y >= m_weights.GetLength(1))
                return 0;

            return m_weights[x, y];
        }

        internal override void SetRayColor(Vector2 a_pixel, ColorFloat a_color)
        {
            a_color = Gamma.LinearToSRGB(a_color);

            int start_x = (int)Math.Floor(a_pixel.X - Filter.Ray);
            int end_x = (int)Math.Ceiling(a_pixel.X + Filter.Ray) - 1;

            int start_y = (int)Math.Floor(a_pixel.Y - Filter.Ray);
            int end_y = (int)Math.Ceiling(a_pixel.Y + Filter.Ray) - 1;

            for (int y = start_y; y <= end_y; y++)
            {
                int cy = m_overlay_corrector.CorrectY(y);

                for (int x = start_x; x <= end_x; x++)
                {
                    int cx = m_overlay_corrector.CorrectX(x);
                    double w = GetWeight((x + 0.5) - a_pixel.X, (y + 0.5) - a_pixel.Y);
                    m_pixels[cx, cy].Collect(a_color, w);
                }
            }

            #if DEBUG
            for (int y = start_y - 1; y <= end_y + 1; y++)
            {
                Debug.Assert(GetWeight((start_x + 0.5 - 1) - a_pixel.X, (y + 0.5) - a_pixel.Y).IsAlmostEquals(0));
                Debug.Assert(GetWeight((end_x + 0.5 + 1) - a_pixel.X, (y + 0.5) - a_pixel.Y).IsAlmostEquals(0));
            }
            for (int x = start_x; x <= end_x; x++)
            {
                Debug.Assert(GetWeight((x + 0.5) - a_pixel.X, (start_y + 0.5 - 1) - a_pixel.Y).IsAlmostEquals(0));
                Debug.Assert(GetWeight((x + 0.5) - a_pixel.X, (end_y + 0.5 + 1) - a_pixel.Y).IsAlmostEquals(0));
            }
            #endif
        }

        public override ResamplerType ResamplerType
        {
            get
            {
                return ResamplerType.LocalGrid;
            }
        }
    }
}